11. Security

Grails는 보안면에 있어서 자바 서블릿과 동일한 수준이며, 더하지도 덜하지도 않다. 하지만 서블릿(Grails를 비롯)은 보안측면에 매우 안전하다. 일반적인 버퍼오버런과 비정상 URL공격에 있어서 광범위하게 안전하며 그 이유는 코드의 토대가 되는 JVM의 특성때문이다.

웹에서의 보안문제는 일반적으로 개발자의 순진함이나 실수로부터 발생하며 Grails에는 일반적인 실수를 방지하거나 보안에 안전한 프로그램을 작성하기 쉽도록 돕는 기능이 있다.

What Grails Automatically Does(Grails가 자동으로 하는 것)

Grails는 기본적으로 안전에 대한 몇 가지 매커니즘을 내장하고 있다.

  1. 모든 표준적인 데이터베이스 접근은 GORM 도메인 객체를 통해 이루어지며 이는 자동적으로 SQL 인젝션 공격으로부터 SQL을 보호한다.
  2. 기본적인 scaffolding은 데이터를 화면에 표시할 때 모든 데이터 필드를 HTML로부터 보호하는 템플릿이다.
  3. Grails는 태그(link, form, createLink, createLinkTo, 기타)를 생성하는 링크는 인젝션 인젝션으로부터 지켜주는 적절한 보호 메커니즘을 사용한다.
  4. Grails는 인젝션 공격을 막기 위해 여러분이 HTML, JavaScript, URL들로 데이터를 표현할 때 쉽게 데이터를 보호하는 코덱(codecs)을 제공한다.

11.1 Securing Against Attacks

SQL injection(SQL 인젝션)

GORM 도메인 클래스에 놓여있는 기술인 하이버네이트는 데이터베이스에 자료를 입력할때 자동으로 데이터를 분리하여 처리한다. 그렇기 때문에 이 것는 그다지 문제거리가 되지 않는다. 하지만 확인하지 않은 요청 파라미터를 사용하여 잘못된 HQL 코드를 만들게 될 가능성이 있다. 예를 들어 아래와 같이 동작하는 것은 HQL 인젝션에 대해 취약하다:

def vulnerable = {
	def books = Book.find("from Book as b where b.title ='" + params.title + "'")
}

이런 식으로 코드를 짜면 안된다. 파라미터로 전달하려면 이름을 통해서나 위치지정 파라미터를 통해서 전달해야 한다:

def safe = {
	def books = Book.find("from Book as b where b.title =?", [params.title])
}

Phishing(피싱)

공개적인 관계들은 고객과의 커뮤니케이션에서 서비스 브랜드를 가로채는 문제를 발생시킨다. 고객들은 받은 메일의 진의를 가려낼 줄 알아야 한다.

XSS - cross-site scripting injection(크로스 사이트 스크립팅 인젝션)

여러분의 어플리케이션은 외부 뿐만 아내라 프로그램 내부적에서 발생한 들어오는 요청에 대해서도 최대한 확인하는 것은 매우 중요하다. 티켓팅과 페이지 플로우 시스템은 이에 대한 도움을 주며 Grails는 기본적으로 이런 보안기능을 가진 스프링 웹 플로우 를 지원한다.

It is important that your application verifies as much as possible that incoming requests were originated from your application and not from another site. Ticketing and page flow systems can help this and Grails' support for Spring Web Flow includes security like this by default.

뷰에 그려지는 모든 데이터의 값들이 옳게 분리됐는지 확인하는 것 또한 매우 중요하다. 예를 들어 HTML이나 XHTML로 데이터를 그려낼 때 다른이들에게 보여질 데이터나 태그에 악의적인 JavaScript나 HTML을 인젝션 하지 못하도록 확실하게 보장해야 하고 이를 위해 모든 객체들에 대해 encodeAsHTML을 호출해야 한다. Grails는 이런 목적에서 다양한 동적 인코딩 메소드(Dynamic Encoding Methods)를 제공한다. 그리고 여러분의 결과물이 사용하는 분리 포맷(escaping format)이 지원되지 않을 경우에는, 그것을 지원하기 위한 사용자만의 코덱을 쉽게 작성 할 수 있다.

사용자에게 전달 될 다음 리다이렉트 주소 정보를 판단하는데 있어서 요청된 파라미터나 데이터 필드의 사용을 하지 말아야 한다. 만약 예를 들어 성공적으로 로그인 한 사용자의 다음 리다이렉팅 페이지의 URL을 successURL 파라미터로 사용한다면, 공격하는 사람은 공격자의 사이트에서 로그인 과정을 흉내낼 것이고 사용자가 로그인하면 그들의 사이트로 리다이렉트 시킬 것이다. 이것은 잠재적으로 자바스크립트 코드가 사이트에서 로그인 된 계정에 대해 잘못된 방법으로 정보를 얻을 수 있기 때문에 가능하다.

HTML/URL injection(HTML/URL 인젝션)

페이지에서 링크를 생성하는 곳에 좋지 않은 데이터가 공급되고 그 링크를 클릭하면 원하지 않는 결과를 나타내거나 원하지 않는 곳으로 사이트를 이동시키거나 요청 파라미터를 변경시킬 것이다.

HTML/URL 인젝션은 Grails가 제공하는 코덱(codecs)으로 쉽게 방지할 수 있다. 그리고 Grails가 지원하는 태그라이브러리를 이용해 적절한 곳에 encodeAsURL 을 사용한다. URL을 생성하는데 있어 자신만의 태그를 만들고자 한다면 이 점을 염두해야 한다.

Denial of service(서비스 거부)

이것은 로드밸런서나 다른 어플라이언스에 유용할 것 같지만 공격자가 만들어낸 링크에 의한 쿼리를 처리하는 것과 관련된 문제에도 관련된다. 예를 들어 공격자가 결과 값을 최대로 설정한다면 쿼리는 서버의 메모리 한도를 초과하게 되거나 시스템이 느려지게 된다. 이것을 해결하기 위한 벙법은 요청 파라미터를 동적 파인더나 다른 GORM 쿼리 메소드에 넘겨주기 전에 언제나 깨끗하게 처리를 하는 것이다:

def safeMax = Math.max(params.max?.toInteger(), 100) // never let more than 100 results be returned
return Book.list(max:safeMax)

Guessable IDs(예측할 수 있는 ID)

많은 어플리케이션들이 GORM이나 다른 곳들에서 객체정보를 받아오기 위해 URL의 뒷부분을 “id” 로서 사용한다. 특별히 GORM의 경우 보통 순서대로 나열된 정수를 사용하기에 이런 점은 쉽게 예측이 가능하다.

그러나 요청하는 사용자가 해당 요청한 객체를 볼 수 있는지 사용자에게 응답하기 전에 확인해야 한다.

이렇게 하지 않으면 “letmein”으로 기본 패스워드를 가지는 것과 같은, “security through obscurity” 보안 문제가 생겨 시스템에 구멍이 날 수 밖에 없다.

모든 보호되지 않은 URL은 한 가지 방법 이상으로 공개적으로 접근될 수 있다는 것을 염두해두어야 한다.

11.2 Encoding and Decoding Strings

Grails는 동적 인코딩/디코딩 메소드의 개념을 지원한다. 표준 코덱들은 Grails에 번들로 포함되어 있다. Grails는 또한 개발자에게 그들이 만든 코덱을 실행시에 인식하기위한 단순한 메카니즘을 지원한다.

Codec Classes(코덱 클래스)

Grails의 코덱 클래스는 encode 클로져를 포함하거나 decode클로져를 포함하거나 둘다 포함할 것이다. Grails 어플리케이션이 시작되면 Grails 프레임워크는 grails-app/utils/ 디렉토리로부터 동적으로 코덱을 불러들이다.

Grails 프레임워크는 grails-app/utils/ 디렉토리에서 Codec으로 이름이 끝나는 클래스들에 대해서 살펴볼 것이다. 예를 들어 Grails가 포함하는 표준 코덱중의 하나는 HTMLCodec이다.

코덱이 코드 블럭이 정의된 encode 프로퍼티를 가지면 Grails는 동적으로 encode 메소드를 생성하고, 그 메소드를 encode 클로져에 정의된 코덱을 나타내는 이름으로 String 클래스에 더할 것이다. 예를 들어 HTMLCodec 클래스는 encode 블럭을 정의하였기 때문에 Grails는 String 클래스에 encodeAsHTML 이란 이름으로 그 클로져를 붙일 것이다.

HTMLCodec과 URLCodec 클래스는 또한 decode블럭을 정의해 놓았다. 따라서 Grails는 이 두 코덱에 대해서 decodeHTML과 decodeURL이란 이름으로 메소드를 추가할 것이다. 동적 코덱 메소드는 Grails 어플리케이션 어디서나 실행될 수 있다. 예를 들어 report에 description이라는 속성이 다고 가정해보자. 이 속성은 HTML 문서에는 표현되지 말아야 하는 특수문자를 포함하고 있다. GSP에서 이를 해결하는 방법은 한 가지이다. description 속성을 동적 인코드 메소드를 사용하여 아래와 같이 인코딩하는 것이 그 방법이다:

${report.description.encodeAsHTML()}

디코딩은 value.decodeHTML() 문법을 이용하여 실행된다.

Standard Codecs(표준 코덱)

HTMLCodec

이 코덱은 HTML escaping과 unescaping을 수행한다. 따라서 이를 이용하면 값은 HTML태그를 가지지 않고 HTML로 표현될 때 문서의 HTML구조를 변형시키지 않고 값을 문서에 그려낼 수 있다. 예를 들어, 주어진 값이 “Don't you know that 2 > 1?” 라면 HTML문서 속에 안전하게 값을 그려낼 수 없다. 값 속의 > 문자는 HTML의 닫는 태그같아 보이기 때문에 이 값을 만약에 tag의 속성으로 사용한다면 잘못된 결과를 나타낼 것이다. 특히 input tag의 속성으로 사용하게 되면 잘못된 결과를 나타낼 수 있다.

사용 예제:

<input name="comment.message" value="${comment.message.encodeAsHTML()}"/>

HTML 인코딩은 apostrophe/single quote를 재인코딩 하지 않는다. 따라서 페이지의 속성 값에는 어퍼스트로피 기호로 값을 지저분하게 하지 않기 위해서는 쌍따옴표를 사용해야 한다.

URLCodec

URL 인코딩은 링크, 폼 액션 혹은 필요할 때 언제나 URL을 생성하는 데 필요하다. URL 인코딩은 URL 사용에 있어서 잘못된 문자를 쓰는것을 막아준다. 예를 들어 “Apple & Blackberry”가 URL에 사용하면 URL이 제대로 동작하지 않는다. GET 요청에서 앰퍼센드(&)는 파라미터를 자르는데 사용되기 때문이다.

사용 예제:

<a href="/mycontroller/find?searchKey=${lastSearch.encodeAsURL()}">Repeat last search</a>

Base64Codec

Base64 인코딩/디코딩 기능을 수행한다. 예를 들어 다음과 같이 사용한다:

Your registration code is: ${user.registrationCode.encodeAsBase64()}

JavaScriptCodec

JavaScriptCodec은 문자열을 올바른 JavaScript 문자열로 사용할 수 있도록 보장한다:

Element.update('${elementId}', '${render(template: "/common/message").encodeAsJavaScript()}')

Custom Codecs(사용자 정의 코덱)

어플리케이션은 자신만의 코덱을 정의할 수 있고 Grails는 표준 코덱들과 함께 사용자가 정의한 코덱을 읽어들인다. 사용자 정의 코덱은 grails-app/utils/ 디렉토리에 정의돼야 하며 클래스 이름은 Codec로 끝나야 한다. 코덱은 정적 encode 블럭, 정적 decode 블럭, 혹은 둘 다 가질 수 있다. 블럭은 하나의 인자를 넘겨야 하는데 이 인자는 동적 메소드가 실행된 객체가 된다.

class PigLatinCodec {
  static encode = { str ->
    // convert the string to piglatin and return the result
  }
}

위의 코덱을 통해 어플리케이션에서는 아래와 같은 일을 할 수 있다:

${lastName.encodeAsPigLatin()}

11.3 Authentication

기본적으로 현재 인증에 관한 기본 메커니즘이 없다고 하더라도 여러가지 다양한 방법으로 인증을 구현할 수 있다. 하지만 interceptorsfilters를 이용하면 인증 메커니즘을 쉽게 구현할 수 있다.

필터는 인증이 모든 컨트롤러나 URI 스페이스에 적용될 수 있다. 예를 들어 grails-app/conf/SecurityFilters.groovy 클래스를 아래와 같은 내용으로 생성할 수 있다:

class SecurityFilters {
   def filters = {
       loginCheck(controller:'*', action:'*') {
           before = {
              if(!session.user && actionName != "login") {
                  redirect(controller:"user",action:"login")
                  return false					
	           }
           }

} } }

loginCheck 필터는 어떤 액션이 실행되기 전에 가로챌 것이다. 그리고 session에 user가 없거나 login 액션을 실행한것이 아니라면 login 액션으로 리다이렉트 한다.

login 액션은 다음과 같이 간단하게 작성할 수 있다:

def login = {
	if(request.get) render(view:"login")
	else {
		def u = User.findByLogin(params.login)
		if(u) {
			if(u.password == params.password) {
				session.user = u
				redirect(action:"home")
			}
			else {
				render(view:"login", model:[message:"Password incorrect"])							
			}
		}
		else {
			render(view:"login", model:[message:"User not found"])			
		}
	}
}

11.4 Security Plug-ins

간단한 인증 과정을 넘어서 권한부여(Authorization)나 role과 같은 기능을 필요로 할 수 있다. 그런 경우, 다음과 같은 보안 플러그인을 사용하는것에 대해 고려해볼 수 있다.

11.4.1 Acegi

Acegi 플러그인은 Spring Acegi 프로젝트에서 만들어졌고 모든 종류의 인증이나 권한부여 스키마를 구축하는데 있어 유연하고 확장성있는 프렘임워크을 제공한다.

Acegi 플러러그인은 URI와 롤(Role)과의 매핑을 지정해야 하며 기본 도메인 모델을 사용자, 권한 설정들, 요청 매핑에 대해서 모델링 하는것을 제공한다. 위키의 문서 를 참고하면 더 자세한 정보를 얻을 수 있다.

11.4.2 JSecurity

JSecurity 는 자바 POJO 지향의 보안 프레임워크이다. JSecurity는 다시 렐름, 사용자, 역할, 권한을 모델링 하는 기본 도메인 모델을 제공한다. JSecurity를 통해 보안을 적용시킬 controller들에 대해서 JSecAuthBase 클래스를 기반으로 controller를 확장하고 역할을 설정하기 위해 accessControl 코드을 제공해야 한다. 아래는 한 예제이다:

class ExampleController extends JsecAuthBase {
    static accessControl = {
        // All actions require the 'Observer' role.
        role(name: 'Observer')

// The 'edit' action requires the 'Administrator' role. role(name: 'Administrator', action: 'edit')

// Alternatively, several actions can be specified. role(name: 'Administrator', only: [ 'create', 'edit', 'save', 'update' ]) }

… }

JSecurity 플러그인에 관련된 더 많은 정보을 알고 싶다면 JSecurity Quick Start를 참고하라.